<?php
/**
 * Celebrio Kernel
 *
 * @copyright  Copyright (c) 2012 Celebio Software, s.r.o.
 * @package    Kernel
 */
namespace KernelModule\LogsModule\PerformanceModule;

use Celebrio\FileSystem\File;

use Nette\Object;
use Nette\Environment;
use Nette\String;

use KernelModule\LogsModule\PerformanceModule\IPerformanceLogsProcessor;

use Celebrio\FileSystem\TextFile;
use Celebrio\Core\Config;
use Celebrio\Diagnostics\Timer;
use Celebrio\Diagnostics\TimerLevel;

/**
 * Azure performance logs in Celebrio
 * 
 * @author Martin Novák
 */
class PerformanceLogsProcessorFile extends Object implements IPerformanceLogsProcessor {
    private static $statisticsFileName = "CelebrioKernelTimers.log";
    private static $timerFileName = "timer.log";
    
	/**
     * Deletes hard logs older than a set number of months
     * @param int $numberOfMonths
     * @return mixed number of deleted logs or FALSE
     */
    public static function deleteHardLogsOlderThan($numberOfMonths){
    	if($numberOfMonths == 0) {
    		self::deleteAllHardLogs();
    		return TRUE;
    	} else {
    		//TODO: implement deleting based on months
    		return FALSE;	
    	}
    }
    
    /**
     * deletes all performance hard logs
     */
    private static function deleteAllHardLogs(){
    	try {
    		$timerFile = new TextFile($logDir.self::$timerFileName);
    	} catch (\FileNotFoundException $ex) {
            \Logger::getRootLogger()->error(self::$timerFileName." not found",$ex);
    		return 0;
    	}
	    $timerFile->clear();
    	Config::current()->saveConfigArray(array("processed" => 0),"ProcessedTimers");
    }
    
    /**
     * deletes statistics and sets processed logs to 0
     */
    public static function deleteStatistics(){
    	try {
    		$file = new TextFile($logDir.self::$statisticsFileName);
    	} catch (\FileNotFoundException $ex) {
            \Logger::getRootLogger()->error(self::$statisticsFileName." not found",$ex);
    		return 0;
    	}
	    $file->clear();
    	Config::current()->saveConfigArray(array("processed" => 0),"ProcessedTimers");
    }

    /**
     * Process performance logs
     * @return int number of processed logs
     */
    public static function processLogs()
    {
    	$processedLogs = array();
    	$timerFile = NULL;
    	$processed = 0;
    	$statisticsFileExists = FALSE;
    	$logDir = Environment::expand("%logDir%");
        if (!String::endsWith($logDir, "/")) {
        	$logDir = $logDir . "/";
        }
        
        if(file_exists($logDir.self::$statisticsFileName)){
        	$statisticsFileExists = TRUE;
        }
        
    	/**
    	 * we retrieve the load the configuration with number of proccessed bytes of timers
    	 */
    	if($statisticsFileExists){
    		$configArray = Config::current()->loadConfigArray("ProcessedTimers");
    		$processed = $configArray["processed"];
    	}else{
	    	Config::current()->saveConfigArray(array("processed" => 0),"ProcessedTimers");
	    	$processed = 0;
    	}

    	try {
    		$timerFile = new TextFile($logDir.self::$timerFileName);
    	} catch (\FileNotFoundException $ex) {
            \Logger::getRootLogger()->error(self::$timerFileName." not found",$ex);
    		return 0;
    	}

    	/**
	     * We retrieve Timer logs starting from last proccessed position with reading time
	     * limit of 20 seconds
	     */
    	$start = time(); // we have time limit for reading stored Timer logs
    	Timer::start("Loading Timer logs");
    	if(!$timerFile->movePointerToPosition($processed)){
    		throw new \InvalidStateException("Performace log processor was not able to move pointer to position in ".self::$timerFileName.". The log might be corrupted. To read the log from beginning, change ProcessedTimers configuration value processed to 0. To begging new statistics, also remove ".self::$statisticsFileName.".");
    		return 0;
    	}
    	$numberOfProcessedLogs = 0;
    	// we read all measured times from timer.log
    	while($log = $timerFile->getLine()){
    		// we let the loop go for maximum of 20 seconds
    		if((time() - $start) >= 20) {
                \Logger::getRootLogger()->error("Performance Logs Processor skipped some Timer logs because of time limit. There is no data curruption but you need to update the statistics again to cover all available logs.");
    			break;
    		}
    		$block = trim(substr($log, 37));
    		$time = substr($log, 18, 16);
    		if(isset($processedLogs[$block])){
    			$processedLogs[$block] = array(
    					($processedLogs[$block][0]+$time),
    					($processedLogs[$block][1]+1));
    		} else {
    			$processedLogs[$block] = array($time,1);
    		}
    		$numberOfProcessedLogs++;
    	}
    	$lastProcessedLogPosition = $timerFile->getPointerPosition();
    	$timerFile->closeFile();
    	Timer::stop("Loading Timer logs");
	    
	    $statisticsFile = NULL;
    	try {
    		$statisticsFile = new TextFile($logDir.self::$statisticsFileName, "a+");
    	} catch (\FileNotFoundException $ex) {
            \Logger::getRootLogger()->error(self::$statisticsFileName." not found",$ex);
    		return 0;
    	}
    	
    	/**
    	 * We retrieve old statistics
    	 */
    	$start = time(); // we count time limit for inserting data into azure storage table
    	Timer::start("Updating performance statistics");
    	$updatedStatistics = array();
    	if($statisticsFileExists) {
    		while($log = $statisticsFile->getLine()){
    			// we let the loop go for maximum of 30 seconds
			    if((time() - $start) >= 30) {
	    			$lastProcessedLogPosition = 0;
                    \Logger::getRootLogger()->error("Performance Logs Processor run out of time limit while updating Timer statistics. Last proccessed position set to 0 so that the logs are not currupted. No statistics were saved.");
    				break;
	    		}
		    	$firstDevider = strpos($log, "§");
    			$secondDevider = strpos($log, "§", $firstDevider+1);
    			$thirdDevider = strpos($log, "§", $secondDevider+1);
    			$block = substr($log, 0, $firstDevider-1);
    			$average = trim(substr($log, $firstDevider+2, ($secondDevider-1-strlen("§")-$firstDevider)));
    			$count = trim(substr($log, $secondDevider+2, ($thirdDevider-1-strlen("§")-$secondDevider)));
    			$time = trim(substr($log, $thirdDevider+2));
		    	if(isset($processedLogs[$block])) {
		    		$processedLog = $processedLogs[$block];
		    		$time 	= ($time + $processedLog[0]);
		    		$count 	= ($count + $processedLog[1]);
		    		$average = ($time / $count);
		    		unset($processedLogs[$block]);
		    	}
		    	$updatedStatistics [] = $block." § ".$average." § ".$count." § ".$time.PHP_EOL;
		    }
    	}
    	
    	/**
	     * we delete and recreate the statistics file (it is faster than updating) and set
	     * processed position to 0 so that in case of failure the next update starts again
	     * from the first log and there is no ongoing data corruption
	     */
	    if($statisticsFileExists){
	    	$statisticsFile->clear();
	    }
    	Config::current()->saveConfigArray(array("processed" => 0),"ProcessedTimers");

    	/**
	     * we store the updated statistics
	     */
	    if($statisticsFileExists) {
		    foreach($updatedStatistics as $statistic){
		    	// we let the loop go for maximum of 30 seconds
			    if((time() - $start) >= 30) {
	    			$lastProcessedLogPosition = 0;
                    \Logger::getRootLogger()->error("Performance Logs Processor run out of time limit while inserting updated Timer statistics. Last proccessed position set to 0 so that the logs are not currupted.");
    				break;
	    		}
		    	$statisticsFile->append($statistic);
		    }
		    unset($updatedStatistics);
	    }

	    /**
	     * we process the rest of the logs
	     */
    	foreach($processedLogs as $block => $log){
    		// we let the loop go for maximum of 30 seconds
	    	if((time() - $start) >= 30) {
    			$lastProcessedLogPosition = 0;
                \Logger::getRootLogger()->error("Performance Logs Processor run out of time limit while inserting new unique Timer statistics. Last proccessed position set to 0 so that the logs are not currupted.");
    			break;
    		}
	    	$time = $log[0];
	    	$count = $log[1];
	    	$average = $log[0] / $log[1];
	    	$statisticsFile->append(trim($block)." § ".$average." § ".$count." § ".$time.PHP_EOL);
    	}
    	$statisticsFile->closeFile();
    	Timer::stop("Updating performance statistics");

	    unset($processedLogs);
	    
	    /**
	     * we log the last proccessed position in Timers log
	     */
	    Config::current()->saveConfigArray(array("processed" => $lastProcessedLogPosition),"ProcessedTimers");
    	
    	return $numberOfProcessedLogs;
    }

}